// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package api

import (
	"context"

	"github.com/cilium/cilium/pkg/slices"
)

// IngressCommonRule is a rule that shares some of its fields across the
// IngressRule and IngressDenyRule. It's publicly exported so the code
// generators can generate code for this structure.
//
// +deepequal-gen:private-method=true
type IngressCommonRule struct {
	// FromEndpoints is a list of endpoints identified by an
	// EndpointSelector which are allowed to communicate with the endpoint
	// subject to the rule.
	//
	// Example:
	// Any endpoint with the label "role=backend" can be consumed by any
	// endpoint carrying the label "role=frontend".
	//
	// +kubebuilder:validation:Optional
	FromEndpoints []EndpointSelector `json:"fromEndpoints,omitempty"`

	// FromCIDR is a list of IP blocks which the endpoint subject to the
	// rule is allowed to receive connections from. Only connections which
	// do *not* originate from the cluster or from the local host are subject
	// to CIDR rules. In order to allow in-cluster connectivity, use the
	// FromEndpoints field.  This will match on the source IP address of
	// incoming connections. Adding  a prefix into FromCIDR or into
	// FromCIDRSet with no ExcludeCIDRs is  equivalent.  Overlaps are
	// allowed between FromCIDR and FromCIDRSet.
	//
	// Example:
	// Any endpoint with the label "app=my-legacy-pet" is allowed to receive
	// connections from 10.3.9.1
	//
	// +kubebuilder:validation:Optional
	FromCIDR CIDRSlice `json:"fromCIDR,omitempty"`

	// FromCIDRSet is a list of IP blocks which the endpoint subject to the
	// rule is allowed to receive connections from in addition to FromEndpoints,
	// along with a list of subnets contained within their corresponding IP block
	// from which traffic should not be allowed.
	// This will match on the source IP address of incoming connections. Adding
	// a prefix into FromCIDR or into FromCIDRSet with no ExcludeCIDRs is
	// equivalent. Overlaps are allowed between FromCIDR and FromCIDRSet.
	//
	// Example:
	// Any endpoint with the label "app=my-legacy-pet" is allowed to receive
	// connections from 10.0.0.0/8 except from IPs in subnet 10.96.0.0/12.
	//
	// +kubebuilder:validation:Optional
	FromCIDRSet CIDRRuleSlice `json:"fromCIDRSet,omitzero"`

	// FromEntities is a list of special entities which the endpoint subject
	// to the rule is allowed to receive connections from. Supported entities are
	// `world`, `cluster`, `host`, `remote-node`, `kube-apiserver`, `ingress`, `init`,
	// `health`, `unmanaged`, `none` and `all`.
	//
	// +kubebuilder:validation:Optional
	FromEntities EntitySlice `json:"fromEntities,omitempty"`

	// FromGroups is a directive that allows the integration with multiple outside
	// providers. Currently, only AWS is supported, and the rule can select by
	// multiple sub directives:
	//
	// Example:
	// FromGroups:
	// - aws:
	//     securityGroupsIds:
	//     - 'sg-XXXXXXXXXXXXX'
	//
	// +kubebuilder:validation:Optional
	FromGroups []Groups `json:"fromGroups,omitempty"`

	// FromNodes is a list of nodes identified by an
	// EndpointSelector which are allowed to communicate with the endpoint
	// subject to the rule.
	//
	// +kubebuilder:validation:Optional
	FromNodes []EndpointSelector `json:"fromNodes,omitempty"`
}

// DeepEqual returns true if both IngressCommonRule are deep equal.
// The semantic of a nil slice in one of its fields is different from the semantic
// of an empty non-nil slice, thus it explicitly checks for that case before calling
// the autogenerated method.
func (in *IngressCommonRule) DeepEqual(other *IngressCommonRule) bool {
	if slices.XorNil(in.FromEndpoints, other.FromEndpoints) {
		return false
	}
	if slices.XorNil(in.FromCIDR, other.FromCIDR) {
		return false
	}
	if slices.XorNil(in.FromCIDRSet, other.FromCIDRSet) {
		return false
	}
	if slices.XorNil(in.FromEntities, other.FromEntities) {
		return false
	}

	return in.deepEqual(other)
}

// IngressRule contains all rule types which can be applied at ingress,
// i.e. network traffic that originates outside of the endpoint and
// is entering the endpoint selected by the endpointSelector.
//
//   - All members of this structure are optional. If omitted or empty, the
//     member will have no effect on the rule.
//
//   - If multiple members are set, all of them need to match in order for
//     the rule to take effect.
//
//   - FromEndpoints, FromCIDR, FromCIDRSet and FromEntities are mutually
//     exclusive. Only one of these members may be present within an individual
//     rule.
type IngressRule struct {
	IngressCommonRule `json:",inline"`

	// ToPorts is a list of destination ports identified by port number and
	// protocol which the endpoint subject to the rule is allowed to
	// receive connections on.
	//
	// Example:
	// Any endpoint with the label "app=httpd" can only accept incoming
	// connections on port 80/tcp.
	//
	// +kubebuilder:validation:Optional
	ToPorts PortRules `json:"toPorts,omitempty"`

	// ICMPs is a list of ICMP rule identified by type number
	// which the endpoint subject to the rule is allowed to
	// receive connections on.
	//
	// Example:
	// Any endpoint with the label "app=httpd" can only accept incoming
	// type 8 ICMP connections.
	//
	// +kubebuilder:validation:Optional
	ICMPs ICMPRules `json:"icmps,omitempty"`

	// Authentication is the required authentication type for the allowed traffic, if any.
	//
	// +kubebuilder:validation:Optional
	Authentication *Authentication `json:"authentication,omitempty"`
}

// IngressDenyRule contains all rule types which can be applied at ingress,
// i.e. network traffic that originates outside of the endpoint and
// is entering the endpoint selected by the endpointSelector.
//
//   - All members of this structure are optional. If omitted or empty, the
//     member will have no effect on the rule.
//
//   - If multiple members are set, all of them need to match in order for
//     the rule to take effect.
//
//   - FromEndpoints, FromCIDR, FromCIDRSet, FromGroups and FromEntities are mutually
//     exclusive. Only one of these members may be present within an individual
//     rule.
type IngressDenyRule struct {
	IngressCommonRule `json:",inline"`

	// ToPorts is a list of destination ports identified by port number and
	// protocol which the endpoint subject to the rule is not allowed to
	// receive connections on.
	//
	// Example:
	// Any endpoint with the label "app=httpd" can not accept incoming
	// connections on port 80/tcp.
	//
	// +kubebuilder:validation:Optional
	ToPorts PortDenyRules `json:"toPorts,omitempty"`

	// ICMPs is a list of ICMP rule identified by type number
	// which the endpoint subject to the rule is not allowed to
	// receive connections on.
	//
	// Example:
	// Any endpoint with the label "app=httpd" can not accept incoming
	// type 8 ICMP connections.
	//
	// +kubebuilder:validation:Optional
	ICMPs ICMPRules `json:"icmps,omitempty"`
}

// RequiresDerivative returns true when the EgressCommonRule contains sections
// that need a derivative policy created in order to be enforced
// (e.g. FromGroups).
func (e *IngressCommonRule) RequiresDerivative() bool {
	return len(e.FromGroups) > 0
}

// IsL3 returns true if the IngressCommonRule contains at least a rule that
// affects L3 policy enforcement.
func (in *IngressCommonRule) IsL3() bool {
	if in == nil {
		return false
	}
	return len(in.FromEndpoints) > 0 ||
		len(in.FromCIDR) > 0 ||
		len(in.FromCIDRSet) > 0 ||
		len(in.FromEntities) > 0 ||
		len(in.FromGroups) > 0 ||
		len(in.FromNodes) > 0
}

// CreateDerivative will return a new rule based on the data gathered by the
// rules that creates a new derivative policy.
// In the case of FromGroups will call outside using the groups callback and this
// function can take a bit of time.
func (e *IngressRule) CreateDerivative(ctx context.Context) (*IngressRule, error) {
	newRule := e.DeepCopy()
	if !e.RequiresDerivative() {
		return newRule, nil
	}
	newRule.FromCIDRSet = make(CIDRRuleSlice, 0, len(e.FromGroups))
	cidrSet, err := ExtractCidrSet(ctx, e.FromGroups)
	if err != nil {
		return &IngressRule{}, err
	}
	newRule.FromCIDRSet = append(newRule.FromCIDRSet, cidrSet...)
	newRule.FromGroups = nil
	return newRule, nil
}

// CreateDerivative will return a new rule based on the data gathered by the
// rules that creates a new derivative policy.
// In the case of FromGroups will call outside using the groups callback and this
// function can take a bit of time.
func (e *IngressDenyRule) CreateDerivative(ctx context.Context) (*IngressDenyRule, error) {
	newRule := e.DeepCopy()
	if !e.RequiresDerivative() {
		return newRule, nil
	}
	newRule.FromCIDRSet = make(CIDRRuleSlice, 0, len(e.FromGroups))
	cidrSet, err := ExtractCidrSet(ctx, e.FromGroups)
	if err != nil {
		return &IngressDenyRule{}, err
	}
	newRule.FromCIDRSet = append(newRule.FromCIDRSet, cidrSet...)
	newRule.FromGroups = nil
	return newRule, nil
}
